package biolabs.mcl

import biolabs.core.{Application, BioObject, Delegate, Interaction, Placeholder, UnaryAction}
import biolabs.mcl.syntax._
import biolabs.util.Types

import java.lang.reflect.Modifier

object ModelDefinition {

	def analyze(md: ModelDefinitionAST, app: Application): AnalysisResult[ModelDefinition] = {
		try {
			AnalysisSuccess(new ModelDefinition(md, app))
		} catch {
			case e: SemanticException => AnalysisError(e.getMessage())
		}
	}

	private def typeToClasses(t: SingleType): (List[Class[_]], List[String]) = {
		t match {
			case at: ActualType =>
				val (classes, typeVars) = typesToClasses(at.typeArguments map { _.asSingleType() })
				(at.rawType::classes, typeVars)
			case tv: TypeVar =>
				(classOf[Placeholder]::Nil, tv.name::Nil)
		}
	}

	private def typesToClasses(types: Seq[SingleType]): (List[Class[_]], List[String]) = {
		types match {
			case head::tail =>
				val headx = typeToClasses(head)
				val tailx = typesToClasses(tail)
				(headx._1 ::: tailx._1, headx._2 ::: tailx._2)
			case Nil =>
				(Nil, Nil)
		}
	}
}

class ModelDefinition(val ast: ModelDefinitionAST, app: Application) {

	val name = ast.name

	private val isAbstract = ast.isAbstract

	// check for duplicate names of type parameters
	{
		val names = scala.collection.mutable.Set[String]()
		for(tp <- ast.typeParams) {
			if(names.contains(tp.name))
				throw new SemanticException("Duplicate type parameter " + tp.name)
			names.add(tp.name)
		}
	}

	// analyze type parameters
	val typeParameters = {
		// Use a mutable map that will be populated with already analyzed
		// (or just being analyzed) type parameters.
		val resolvedTypeParams = scala.collection.mutable.Map[String, TypeVar]()
		val (suc, err) = ast.typeParams map { TypeVar.analyze(_, this, resolvedTypeParams, app) } partition { _.successful }
		if(!err.isEmpty)
			throw new SemanticException(err map { _.getErrorMessage() } mkString "\n")
		suc map { _.getResult() }
	}

	// use an immutable map of type parameters in further
	// analysis of containments, super types, actions, ...
	val typeParameterMap = typeParameters map (tv => (tv.name, tv)) toMap

	// analyze containments
	val containments: List[Containment] = {
		val (suc, err) = ast.containments zip (1 to ast.containments.size) map { case(c, i) => Containment.analyze(c, this, app) } partition { _.successful }
		if(!err.isEmpty)
			throw new SemanticException(err map { _.getErrorMessage() } mkString "\n")
		suc map { _.getResult() }
	}

	// analyze super types
	val superTypes = {
		val (suc, err) = ast.superTypes map { SuperTypeSpec.analyze(_, this, containments, app) } partition { _.successful }
		if(!err.isEmpty)
			throw new SemanticException(err map { _.getErrorMessage() } mkString "\n")
		suc map { _.getResult() }
	}

	// analyze performed (inter)actions
	val actions = {
		val (suc, err) = ast.actions zip (1 to ast.actions.size) map { case (a, i) => PerformedAction.analyze(a, i, this, app) } partition { _.successful }
		if(!err.isEmpty)
			throw new SemanticException(err map { _.getErrorMessage() } mkString "\n")
		suc map { _.getResult() }
	}

	// analyze explicit participations in interactions
	val participations = {
		val (suc, err) = ast.explicitParticipations map { ExplicitParticipation.analyze(_, this, app) } partition { _.successful }
		if(!err.isEmpty)
			throw new SemanticException(err map { _.getErrorMessage() } mkString "\n")
		suc map { _.getResult() }
	}

	def materialize(typeArgs: List[MultiType]): SelfRef =
		new ParameterizedSelfRef(this, typeArgs)

	def asType(): SelfRef =
		new ExactSelfRef(this)

	lazy val code = objectCode + "\n" + classCode

	lazy val objectCode: String = {
		val res = new StringBuilder()

		// name and body opening
		res ++= "object " ++= name ++= " {\n"

		// child selectors
		{
			// collect all desired variants of child selectors
			val variantMap = containments map {(_, scala.collection.mutable.Set[List[Int]]())} toMap
			val desiredVariants = superTypes flatMap {_.initArgs} collect { case a: ChildSelectorExpr => a }
			for(dv <- desiredVariants)
				variantMap(dv.children) += dv.variant

			for(c <- containments) {
				res ++= "\t" ++= "final val " ++= c.name ++= " = new "
			}
			// TODO
		}

		// body closing
		res ++= "}\n"

		res.toString()
	}

	lazy val classCode: String = {
		val res = new StringBuilder()

		// distinguish primary and secondary constructor parameters
		val (primaryParams, secondaryParams) = ast.params partition { _.primary }

		// modifiers
		if(isAbstract)
			res ++= "abstract "

		// class name
		res ++= "class " ++= name

		// type parameters
		if(!typeParameters.isEmpty)
			res ++= typeParameters map { ScalaCodeGen.declare(_) } mkString("[", ", ", "]")

		// primary constructor parameters
		if(!primaryParams.isEmpty)
			res ++= primaryParams map { ScalaCodeGen.declare(_) } mkString("(", ", ", ")")

		// super types
		res ++= " extends "
		if(!superTypes.isEmpty)
			res ++= (superTypes map { ScalaCodeGen.extendsCode(_) } mkString " with ") ++= " with "
		res ++= " BioObject"

		// body opening
		res ++= "\n{\n"

		// constructors and initialization
		secondaryParams match {
			// when there are no secondary constructor parameters,
			// just initialize the traits in the primary constructor
			case Nil =>
				res ++= traitInitCode("\t")

			// when there are some secondary constructor parameters,
			// define a secondary constructor and an init method
			// for initializing the traits
			case _ =>
				// secondary constructor
				res ++= "\t" ++= "def this(" ++= (ast.params map { ScalaCodeGen.declare(_) } mkString ", ") ++= ") {\n"
				// call the primary constructor
				res ++= "\t\t" ++= "this(" ++= (primaryParams map { _.name } mkString ", ") ++= ")\n"
				// call the init method
				res ++= "\t\t" ++= "init(" ++= (secondaryParams map { _.name } mkString ", ") ++= ")\n"
				res ++= "\t" ++= "}\n"

				// init method
				res ++= "\t" ++= "def init(" ++= (secondaryParams map { ScalaCodeGen.declare(_) } mkString ", ") ++= ") {\n"
				res ++= traitInitCode("\t\t")
				res ++= "\t" ++= "}\n"
		}

		// Implement the Instantiable trait, i.e. the newInstance() method,
		// if necessary.
		// The newInstance() method calls the primary constructor
		// with the same parameters as this instance was created with.
		// If there is an init(...) method (i.e. there are some secondary
		// parameters), it is not called. It is up to the user to call
		// the init(...) method or to initialize the traits by other means,
		// typically in interaction outcome handlers.
		if(!isAbstract && (superTypes exists { _.isInstantiable })) {
			res ++= "\t" ++= "override def newInstance() =\n"
			res ++= "\t\t" ++= "new " ++= name ++= "(" ++= (primaryParams map { _.name } mkString ", ") ++= ")\n"
		}

		res ++= "\n"

		// children
		for(c <- containments) {
			res ++= "\t" ++= "final val " ++= c.name ++= " = "
			res ++= (c match {
				case sc: SingletonContainment =>
					sc.initExpr
				case mc: MultiContainment =>
					ScalaCodeGen.constructCode(mc.containerSpec)
			})
			res ++= "\n"
		}

		res ++= "\n"

		// priority queue of actions and interactions
		res ++= "\t" ++= "private val __actionQueue = new java.util.PriorityQueue[Scheduled](" + (1 + actions.size) + ")\n"
		res ++= "\t" ++= "__actionQueue.add(Scheduled.NEVER)\n"
		for(a <- actions) {
			res ++= "\t" ++= "__actionQueue.add(\n"
				res ++= genScheduledAction(a, "\t\t")
			res ++= "\t" ++= ")\n"
		}

		res ++= "\n"

		// the proceed method
		res ++= genPureProceedMethod("\t")
		res ++= "\n"
		res ++= proceedMethod

		// the close method
		res ++= genCloseMethod("\t")

		// explicit delegates together with their wrapped versions
		for(p <- participations)
			res ++= genExplicitDelegate(p, "\t")

		// create a wrapped delegate for each inherited delegate
		// candidate that doesn't have one yet (i.e. there is no
		// method called wrapped__<delegate_method_name> declared
		// in the same type as the delegate method)
		for(st <- superTypes)
			for(dc <- st.superType.delegateCandidates)
				st.getMethod("wrapped__" + dc.method.getName(), Array()) match {
					case Some(method) =>
						if(!dc.method.getDeclaringClass().isAssignableFrom(method.getDeclaringClass()))
							res ++= genWrappedDelegate(dc, "\t")
					case None =>
						res ++= genWrappedDelegate(dc, "\t")
				}

		// body closing
		res ++= "\n}"

		res.toString()
	}

	// code for initializing the traits
	private def traitInitCode(indent: String) =
		superTypes map { ScalaCodeGen.initCode(_) } collect {
			case Some(code) => indent + code + "\n"
		} mkString ""

	private val proceedMethod =
"""	override def proceed(from: Long, to: Long, ctx: BioContext) {
		var start = from

		// repeatedly proceed to the next interaction time and perform corresponding interactions
		while(__actionQueue.peek().time <= to) {

			// get time t of next interaction
			val t = __actionQueue.peek().time

			// pure proceed to t
			__pureProceed(start, t, ctx)
			start = t

			// perform interactions
			while(__actionQueue.peek().time == t) {
				val a = __actionQueue.poll()
				a.perform()
				if(a.moveOn())
					__actionQueue.offer(a)
			}
		}

		if(start < to)
			__pureProceed(start, to, ctx)
	}
"""

	private def genPureProceedMethod(indent: String): String = {
		val res = new StringBuilder()

		// method header
		res ++= indent ++= "private def __pureProceed(from: Long, to: Long, ctx: BioContext) {\n"

		// proceed all supertypes
		for(st <- superTypes) {
			if(st.extendsBioObject) {
				res ++= genProceedingCode("super[" + st.superType.name + "]", "from", "to", "ctx", st.timeScale, indent + "\t")
			}
		}

		// proceed all children
		for(c <- containments)
			if(c.tpe.isDescendantOf(classOf[BioObject]))
				res ++= (c match {
					case _: SingletonContainment =>
						genProceedingCode(c.name, "from", "to", "ctx", c.timeScale, indent + "\t")
					case _: MultiContainment =>
						genProceedingCode("for(child <- " + c.name + ") child", "from", "to", "ctx", c.timeScale, indent + "\t")
				})

		// method closing
		res ++= indent ++= "}\n"

		res.toString()
	}

	private def genProceedingCode(subject: String, fromId: String, toId: String, ctxId: String, timeScale: Option[(String, String)], indent: String): String = {
		timeScale match {
			case None =>
				indent + subject + ".proceed(" + fromId + ", " + toId + ", " + ctxId + ")\n"
			case Some((p, q)) =>
				val start = fromId + " * (" + p + ") / (" + q + ")"
				val end = toId + " * (" + p + ") / (" + q + ")"
				indent + "{\n" +
				indent + "\t" + "val (start, end) = (" + start + ", " + end + ")\n" +
				indent + "\t" + "if(start < end)\n" +
				indent + "\t\t" + subject + ".proceed(start, end, " + ctxId + ")\n" +
				indent + "}\n"
		}
	}

	private def genCloseMethod(indent: String): String = {
		// get supertypes with non-abstract close() method
		val closableSupertypes = superTypes filter {
			_.getMethod("close", Array()) match {
				case Some(m) => !Modifier.isAbstract(m.getModifiers())
				case None => false
			}
		}

		indent + "override def close() = {\n" +
		(closableSupertypes map { indent + "\t" + "super[" + _.superType.name + "].close()\n"} mkString "") +
		indent + "}\n"
	}

	private def genScheduledAction(action: PerformedAction, indent: String): String = {
		val res = new StringBuilder()

		res ++= indent ++= "new biolabs.core.Scheduled("
		res ++= (action.ast.actionTimeSpec match {
			case Some(schedule) => schedule
			case None => "every(1)"
		})
		res ++= ", " ++= action.declarationOrder.toString() ++= ") {\n"

			res ++= indent ++= "\t" ++= "private val action = " + action.instantiationCode ++= "\n\n"

			res ++= indent ++= "\t" ++= "override def perform() {\n"
				if(action.isInstanceOf[PerformedInteraction])
					res ++= indent ++= "\t\t" ++= "val delegates = getDelegates()\n"
				res ++= indent ++= "\t\t" ++= "val res = action.perform(" + name + ".this"
				if(action.isInstanceOf[PerformedInteraction])
					res ++= ", delegates"
				res ++= ", notifyProgress)\n"
				res ++= indent ++= "\t\t" ++= "handleResult(res)\n"
				if(action.isInstanceOf[PerformedInteraction]) {
					res ++= indent ++= "\t\t" ++= "for(d <- delegates)\n"
					res ++= indent ++= "\t\t\t" ++= "d.commit()\n"
				}
			res ++= indent ++= "\t" ++= "}\n\n"

			res ++= genProgressNotificationFunction(action, indent + "\t", "notifyProgress")

			res ++= genResultHandlingMethod(action, indent + "\t", "handleResult")

			action match {
				case ia: PerformedInteraction =>
					res ++= genDelegateGatheringMethod(ia, indent + "\t", "getDelegates")
				case _ =>
					// do nothing
			}
		res ++= indent ++= "}"

		res.toString()
	}

	/**
	 * Generates the progress notification function.
	 * The progress notification function first calls all progress approvers
	 * (methods of all supertypes annotated by {@code @ProgressApprover})
	 * for the given action. If all of them return positive result, the
	 * progress notification function proceeds to call all progress handlers
	 * (methods of all supertypes annotated by {@code @ProgressHandler})
	 * for the given action.
	 */
	private def genProgressNotificationFunction(action: PerformedAction, indent: String, funName: String): String = {
		val res = new StringBuilder()
		res ++= indent ++= "private val " ++= funName ++= ": " ++= ScalaCodeGen.refer(action.progressType) ++= "=>ProgressApprovalResult = (progress => {\n"

		res ++= indent ++= "\t" ++= "var r: ProgressApprovalResult = Approved\n"

		// go through all progress approvers of all supertypes
		for(st <- superTypes) {
			for(pa <- st.superType.progressApprovers) {
				// if approver's action is a supertype of the performed action, call the approver
				if(pa.actionType.isAssignableFrom(action.actionType)) {
					res ++= indent ++= "\t" ++= "r = r combine super[" ++= st.superType.name ++= "]." ++= pa.method.getName() ++= "(progress)\n"
				}
			}
		}

		// if progress not approved, stop here
		res ++= indent ++= "\t" ++= "if(!r.approved())\n"
		res ++= indent ++= "\t\t" ++= "return r\n"

		// go through all progress handlers of all supertypes
		for(st <- superTypes) {
			for(ph <- st.superType.progressHandlers) {
				// if handler's action is a supertype of the performed action, call the handler
				if(ph.actionType.isAssignableFrom(action.actionType)) {
					res ++= indent ++= "\t" ++= "super[" ++= st.superType.name ++= "]." ++= ph.method.getName() ++= "(progress)\n"
				}
			}
		}

		res ++= indent ++= "\t" ++= "return r\n"

		res ++= indent ++= "})\n"
		res.toString()
	}

	private def genResultHandlingMethod(action: PerformedAction, indent: String, methodName: String): String = {
		val res = new StringBuilder()
		res ++= indent ++= "private def " ++= methodName ++= "(result: " ++= ScalaCodeGen.refer(action.resultType) ++= ") {\n"

		// go through all result handlers of all supertypes
		for(st <- superTypes) {
			for(rh <- st.superType.resultHandlers) {
				// if handler's action is a supertype of the performed action, call the handler
				if(rh.actionType.isAssignableFrom(action.actionType)) {
					res ++= indent ++= "\t" ++= "super[" ++= st.superType.name ++= "]." ++= rh.method.getName() ++= "(result)\n"
				}
			}
		}

		res ++= indent ++= "}\n"
		res.toString()
	}

	private def genDelegateGatheringMethod(ia: PerformedInteraction, indent: String, methodName: String): String = {
		val res = new StringBuilder()

		// method header
		res ++= indent ++= "private def " ++= methodName ++= "(): Iterable[" ++= ScalaCodeGen.refer(ia.delegateType) ++= "] = {\n"

		// distinguish single-instance children and collections of children
		val (singles, multis) = containments partition { _.isInstanceOf[SingletonContainment] }

		// code for delegates of single-instance children
		val singlesCode = "List(" + (singles map (c => c.name + "." + getWrappedDelegateMethod(c.tpe, ia.actionType)) mkString ", ") + ")"

		// code for delegates from collections of children
		val multisCode = (multis map (c => c.name + ".getChildren()." + getWrappedDelegateMethod(c.tpe, ia.actionType)) mkString " ++ ")

		// combine the codes so that we always return an (eventually empty) collection,
		// but avoid unnecessary concatenation with a dummy empty collection
		res ++= indent ++= "\t"
		if(multis.isEmpty)
			res ++= singlesCode
		else if(singles.isEmpty)
			res ++= multisCode
		else
			res ++= singlesCode ++= " ++ " ++= multisCode

		// method closing
		res ++= indent ++= "}\n"

		res.toString
	}

	private def getWrappedDelegateMethod(t: MultiType, action: ActualType): String =
		"wrapped__"  + t.getDelegateMethod(action).method.getName()

	private def genExplicitDelegate(p: ExplicitParticipation, indent: String): String = {
		// generate method name
		val methodName = ScalaCodeGen.id(p.interaction) + "__delegate"

		val res = new StringBuilder()

		// @Delegate annotation
		val(classes, typeVars) = ModelDefinition.typeToClasses(p.interaction)
		res ++= indent ++= "@" ++= classOf[Delegate].getCanonicalName() ++= "(\n"
		res ++= indent ++= "\t" ++= "action={" ++= (classes map { "classOf[" + _.getCanonicalName() + "]" } mkString(", ")) ++= "},\n"
		res ++= indent ++= "\t" ++= "typeParamRefs={" ++= (typeVars map {'"' + _ + '"'} mkString ", ") ++= "}\n"
		res ++= indent ++= ")\n"

		// method header and body opening
		res ++= indent
		if(inheritsMethod(methodName, Array()))
			res ++= "override "
		res ++= "def " ++= methodName ++= "() = {\n"

		// method body
		p.delegateCombination match {
			case SimpleDelegateCombination(_, dd) =>
				res ++= indent ++= "\t" ++= "super[" ++= dd.superType.name ++= "]." ++= dd.delegateMethod.getName() ++= "()\n"
			case MultiDelegateCombination(_, dds, combinator) =>
				res ++= indent ++= "\t" ++= "val combinator = " ++= combinator ++= "\n"
				val delegates = dds map (dd => "super[" + dd.superType.name + "]." + dd.delegateMethod.getName() + "()")
				res ++= indent ++= "\t" ++= "val delegates = " ++= (delegates mkString("Seq(", ", ", ")\n"))
				res ++= indent ++= "\t" ++= "combinator(delegates)\n"
		}

		// method body closing
		res ++= indent ++= "}\n"

		// create a wrapped delegate for the generated explicit delegate
		res ++= genWrappedDelegate(methodName, MultiType(p.interaction), indent)

		res.toString
	}

	private def genWrappedDelegate(dm: DelegateMethod, indent: String): String = {
		genWrappedDelegate(dm.method.getName(), dm.interaction, indent)
	}

	private def genWrappedDelegate(forMethod: String, action: MultiType, indent: String): String = {
		val res = new StringBuilder()

		val methodName = "wrapped__" + forMethod

		// method header
		res ++= indent
		if(inheritsMethod(methodName, Array()))
			res ++= "override "
		res ++= "def " ++= methodName ++= "() = {\n"

		// method body
		res ++= indent ++= "\t" ++= "new biolabs.core.WrappedDelegate(" ++= forMethod ++= "(), this) {\n"
		res ++= genCommitMethod(action, indent + "\t\t")
		res ++= indent ++= "\t" ++= "}\n"

		// method closing
		res ++= indent ++= "}\n"

		res.toString
	}

	private def genCommitMethod(action: MultiType, indent: String): String = {
		val res = new StringBuilder()

		// method header
		res ++= indent ++= "override def commit() {\n"

		// method body;
		// call all outcome handlers whose action
		// is a supertype of the performed action
		for(st <- superTypes)
			for(oh <- st.superType.outcomeHandlers)
				if(oh.interaction.isAssignableFrom(action))
					res ++= indent ++= "\t" ++= "super[" ++= st.superType.name ++= "]." ++= oh.method.getName() ++= "(delegate)\n"

		// method closing
		res ++= indent ++= "}\n"

		res.toString
	}

	private def inheritsMethod(name: String, parameterTypes: Array[SingleType]): Boolean =
		superTypes exists { _.getMethod(name, parameterTypes).isDefined }
}
